﻿/*
 * This file is part of Viper.  Viper is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, version 2.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright GruntXProductions
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Viper
{
	[Serializable]
    public class Instruction
    {
        public byte[] Bytecode;
        public string Mnemonic;
		public string Operand = "";
		public uint Address;
		public List<Relocation> RelocData = new List<Relocation>();
		public virtual void Relocate(ref List<Symbol> symbols)
		{
			Assembly asm = new Assembly();
			asm.Emit(new Opcodes.push_ptr("hello"));
			
		}

    }
	[Serializable]
	public class Symbol
	{
		public string Name;
		public uint Address;
		public bool Global = false;
	}
	[Serializable]
	public class Label : Symbol
	{
		public bool External = false;
		
		public Label(string name)
		{
			this.Name = name;
		}
	}
	[Serializable]
	public class Variable : Symbol
	{
		public byte[] data;
	}
	[Serializable]
	public class Buffer : Symbol
	{
		public uint size;
	}
	[Serializable]
	public class Relocation
	{
		public uint Address;
		public string Symbol;
		public uint Index;
		public byte RelocationType;
	}
	[Serializable]
    public partial class Assembly
    {
		public uint position = 0;
		public List<Symbol> SymbolTable = new List<Symbol>();
		public NameValueCollection Metadata = new NameValueCollection();
		public List<Relocation> RelocData = new List<Relocation>();
		public byte[] data = new byte[]{};
		private bool stopUntilNewLabel = false;
		public void InsertInstruction(Instruction i, uint address)
		{
			
		}
		public Symbol LookupSymbol(uint addr)
		{
			foreach(Symbol s in SymbolTable)
				if(s.Address == addr)
					return s;
			return null;
		}
		public object Clone()
		{ 
			using (MemoryStream ms = new MemoryStream())
			{
				BinaryFormatter formatter = new BinaryFormatter();
				formatter.Serialize(ms, this);
				ms.Position = 0;
				return formatter.Deserialize(ms);
			}
		}
		private uint getSymbolTableIndex(string symname)
		{
			for(uint i = 0; i < (uint)SymbolTable.Count;i++)
			{
				if(SymbolTable[(int)i].Name == symname)
					return i;
			}
			return 0;
		}
		
		public void Emit(Instruction i)
		{
			if(stopUntilNewLabel)
				return;
			
			i.Address = position;
			if(i is Opcodes.ret)
			{
				if(innerCode.Count > 0)
				{
					if(innerCode[innerCode.Count - 1] is Opcodes.ret)
						return;
				}
			}
			
			
			this.position += (uint)i.Bytecode.Length;
			
			innerCode.Add(i);
		}
		private string getMetaString()
		{
			StringBuilder ret = new StringBuilder("");
			foreach(string key in Metadata.Keys)
			{
				ret.Append(key + "=" + Metadata[key] + "\n");
			}
			return ret.ToString();
		}
		public uint CodeSize
		{
			get
			{
				uint ret = 0;
				foreach(Instruction il in this.innerCode)
					ret += (uint)il.Bytecode.Length;
				return ret;
			}
		}
		public void Merge(Assembly lib)
		{
			
			uint tmp_pos = this.CodeSize;
			this.position = this.CodeSize;
			this.data = lib.data;
			uint pos = 0;
			uint sym_size = 0;
			foreach(Symbol s in this.SymbolTable)
			{
				if(s is Variable)
				{
					sym_size += (uint)((Variable)s).data.Length;
				}
				else if(s is Buffer)
					sym_size += ((Buffer)s).size;
			}
			foreach(Relocation r in lib.RelocData)
			{
				pos = 0;
				foreach(Instruction il in lib.innerCode)
				{
					if(pos + 2 == r.Address)
					{
						uint dat = BitConverter.ToUInt32(il.Bytecode,2);
						byte[] new_dat = BitConverter.GetBytes(dat + this.position + sym_size);
						il.Bytecode = new byte[]{il.Bytecode[0],il.Bytecode[1],new_dat[0],new_dat[1],new_dat[2],new_dat[3]};
					}
					pos += (uint)il.Bytecode.Length;
				}
				r.Symbol += this.SymbolTable.Count;
				r.Address += this.CodeSize;
				this.RelocData.Add(r);
			}
			foreach(Instruction il in lib.innerCode)
			{
				Emit(il);
			}
			foreach(Symbol s in lib.SymbolTable)
			{
				s.Address += tmp_pos ;
				this.SymbolTable.Add(s);
			}
			
		}
		public unsafe void SaveGEX(string output)
		{
			GEXHdr* hdr = stackalloc GEXHdr[sizeof(GEXHdr)];
			setString(hdr->magic,"GEX4 SC");
			hdr->sectionTable = (uint)sizeof(GEXHdr);
			hdr->sectionCount = 5;
			uint textStart = (uint)(sizeof(GEXHdr) + (5 * sizeof(GEXSHdr)));
			GEXSHdr* text = stackalloc GEXSHdr[sizeof(GEXSHdr)];
			GEXSHdr* data = stackalloc GEXSHdr[sizeof(GEXSHdr)];
			GEXSHdr* reloc = stackalloc GEXSHdr[sizeof(GEXSHdr)];
			GEXSHdr* meta = stackalloc GEXSHdr[sizeof(GEXSHdr)];
			GEXSHdr* symtab = stackalloc GEXSHdr[sizeof(GEXSHdr)];
			
			setString(text->name,".text");
			setString(data->name,".data");
			setString(reloc->name,".reloc");
			setString(meta->name,".meta");
			setString(symtab->name,".symtab");
			uint pos = 0;
			uint textSize = 0;
			text->offset = textStart;
			data->offset = this.position + textStart;
			uint relocSize = (uint)(9 * (uint)this.RelocData.Count);
			
			foreach(Symbol i in this.SymbolTable)
			{
				if(i is Buffer)
				{
					i.Address =this.position + pos;
					pos += ((Buffer)i).size;
				}
				else if(i is Variable)
				{
					i.Address = this.position + pos;//pos;
					pos += (uint)((Variable)i).data.Length;
				}
				
			}	
			pos += (uint) this.data.Length;
			foreach(Instruction i in this.innerCode)
			{
				i.Relocate(ref this.SymbolTable);
				relocSize += (uint)i.RelocData.Count * 9;
			}
			foreach(Symbol s in this.SymbolTable)
				if(!s.Global)
					s.Name = "";
			foreach(Instruction il in this.innerCode)
				textSize += (uint)il.Bytecode.Length;
			text->size = textSize;
			data->size = pos;
			reloc->offset = data->offset + data->size;
			reloc->size = relocSize;
			meta->offset = relocSize + reloc->offset;
			meta->size = (uint)getMetaString().Length;
			symtab->offset = meta->size + meta->offset;
			symtab->size = (uint)this.SymbolTable.Count;
			BinaryWriter bw= new BinaryWriter(new FileStream(output,FileMode.Create));
			bw.BaseStream.Position = 0;
			for(uint i = 0; i < (uint)sizeof(GEXHdr);i++)
				bw.Write(((byte*)hdr)[i]);
			for(uint i = 0; i < (uint)sizeof(GEXSHdr);i++)
				bw.Write(((byte*)text)[i]);
			for(uint i = 0; i < (uint)sizeof(GEXSHdr);i++)
				bw.Write(((byte*)data)[i]);	
		 	for(uint i = 0; i < (uint)sizeof(GEXSHdr);i++)
			 	bw.Write(((byte*)reloc)[i]);	
		 	for(uint i = 0; i < (uint)sizeof(GEXSHdr);i++)
			 	bw.Write(((byte*)meta)[i]);	
			for(uint i = 0; i < (uint)sizeof(GEXSHdr);i++)
				bw.Write(((byte*)symtab)[i]);
			uint tmp1 = 0;
			
			foreach(Viper.Instruction il in innerCode)
			{
				if(il.Bytecode.Length - 2 != il.Bytecode[1])
					Console.WriteLine("WTF WTF WTF WTF" + il.Bytecode[0].ToString("X2"));
				foreach(byte b in il.Bytecode)
				{
					tmp1++;
					bw.Write((byte)b);
				}
				
			}
			foreach(Symbol i in this.SymbolTable)
			{
		
				if(i is Buffer)
				{
					for(uint d = 0; d < ((Buffer)i).size;d++)
					{
						bw.Write((byte)0);
					}
				}
				else if(i is Variable)
				{
					for(uint d = 0; d < ((Variable)i).data.Length;d++)
					{
						bw.Write((byte)((Variable)i).data[d]);
					}
				}
			}
			foreach(byte b in this.data)
			{
				bw.Write((byte)b);
			}
			foreach(Viper.Instruction il in innerCode)
			{
				foreach(Relocation r in il.RelocData)
				{
					bw.Write((uint)r.Address);
					bw.Write((uint)getSymbolTableIndex(r.Symbol));
					bw.Write((byte)r.RelocationType);
				}
			}
			foreach(Relocation r in this.RelocData)
			{
				bw.Write((uint)r.Address);
				bw.Write((uint)r.Index);
				bw.Write((byte)r.RelocationType);
			}
			foreach(byte b in getMetaString())
			{
				bw.Write((byte)b);
			}
			foreach(Viper.Symbol sym in SymbolTable)
			{
				foreach(char c in sym.Name)
					bw.Write((byte)c);
				bw.Write((byte)0);
				//Console.WriteLine(sym.Name + " is at " + sym.Address);
				bw.Write((uint)sym.Address);
			}
		    
			bw.Close();
		}
		public void Save(string output)
		{
			SaveGEX(output);
			return;
			BinaryWriter bw = new BinaryWriter(new FileStream(output,FileMode.Create));	
			uint pos = this.position;
			
			foreach(Symbol i in this.SymbolTable)
			{
				if(i is Buffer)
				{
					i.Address = pos;
					pos += ((Buffer)i).size;
				}
				else if(i is Variable)
				{
					i.Address = pos;
					pos += (uint)((Variable)i).data.Length;
				}
			}
			foreach(Instruction i in innerCode)
			{
				i.Relocate(ref SymbolTable);
			    bw.Write(i.Bytecode);
			}
			foreach(Symbol i in this.SymbolTable)
			{
				if(i is Buffer)
				{
					for(uint d = 0; d < ((Buffer)i).size;d++)
					{
						bw.Write((byte)0);
					}
				}
				else if(i is Variable)
				{
					for(uint d = 0; d < ((Variable)i).data.Length;d++)
					{
						bw.Write((byte)((Variable)i).data[d]);
					}
				}
			}
			bw.Close();
		}
		public void CreateString(string name, string text)
		{
			Variable v = new Variable();
			v.data = System.Text.Encoding.ASCII.GetBytes(text + "\0");
			v.Name = name;
			this.SymbolTable.Add(v);
		}
		public void CreateExternalSymbol(string name)
		{
			Label lbl = new Label(name);
			lbl.Address = position;
			lbl.External = true;
			SymbolTable.Add(lbl);
		}
		public void CreateBuffer(string name,uint size)
		{
			Buffer b = new Buffer();
			b.Name = name;
			b.size = size;
			this.SymbolTable.Add(b);
		}
		public void CreateLabel(string name)
		{
			stopUntilNewLabel = false;
			Label lbl = new Label(name);
			lbl.Address = position;
			SymbolTable.Add(lbl);
		}
		public void CreateGlobalLabel(string name)
		{
			stopUntilNewLabel = false;
			Label lbl = new Label(name);
			lbl.Address = position;
			lbl.Global = true;
			SymbolTable.Add(lbl);
		}
		private unsafe static void setString(void* dest,string str)
		{
			for(int i =0; i < str.Length;i++)
				((byte*)dest)[i] = (byte)str[i];
		}
        public List<Instruction> innerCode = new List<Instruction>();
    }
}
